package http

import (
	"bytes"
	"crypto/tls"
	"mime/multipart"
	"net/http"
	"sync"
	"time"
)

var DefaultClient = NewClient()

func client() *http.Client {
	return &http.Client{
		Transport: &http.Transport{
			//连接结束后会直接关闭,
			//否则会加到连接池复用
			DisableKeepAlives: true,
			TLSClientConfig: &tls.Config{
				//设置可以访问HTTPS
				InsecureSkipVerify: true,
			},
		},
		//设置连接超时时间,连接成功后无效
		//连接成功后数据读取时间可以超过这个时间
		//数据读取超时等可以nginx配置
		Timeout: time.Second * 10,
	}
}

type Client struct {
	*http.Client
	url      string                 //网址
	query    map[string]interface{} //query参数,?后面的参数
	header   http.Header            //header参数
	cookie   []*http.Cookie         //header参数一部分,cookie
	body     []byte                 //body参数
	bodyBind interface{}            //响应的body解析
	debug    bool                   //debug模式
	retry    uint                   //重试次数,0不重试
	try      uint                   //已经执行次数,大于等于重试次数则结束
	init     []func(*Client)        //初始化,解决请求之后参数重置的问题
	mu       sync.RWMutex           //锁
}

// 判断是否完成,是否需要重试
func (c *Client) done() bool {
	return c.try >= c.retry
}

// reset 重置参数
// 采用指针类型,所以每次新请求参数都需要重新填写
// 加入预设值,重置之后运行预设方法
// 解决了客户端复用的问题(引用类型,复制客户端不一致问题)
func (c *Client) reset() *Client {
	c.url = ""
	c.query = make(map[string]interface{})
	c.header = map[string][]string{
		"User-Agent":   {"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_13_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/79.0.3945.88 Safari/537.36"},
		"Content-Type": {"application/json;charset=utf-8"}, //发送的数据格式
		"Accept":       {"application/json"},               //希望接收的数据格式
		"Connection":   {"close"},                          //短连接
	}
	c.body = []byte{}
	c.bodyBind = nil
	c.debug = false
	c.retry = 0
	c.try = 0
	for _, v := range c.init {
		v(c)
	}
	return c
}

// Url 设置请求地址
func (c *Client) Url(s string) *Client {
	c.url = s
	return c
}

// SetQuery 设置query参数,已存在则覆盖
func (c *Client) SetQuery(key string, val interface{}) *Client {
	c.mu.Lock()
	defer c.mu.Unlock()
	c.query[key] = val
	return c
}

// SetQueryMap 批量设置query参数,已存在则覆盖
func (c *Client) SetQueryMap(m map[string]interface{}) *Client {
	for i, v := range m {
		c.SetQuery(i, v)
	}
	return c
}

// copyHead 复制请求头,map是引用类型
func (c *Client) copyHead() map[string][]string {
	c.mu.RLock()
	defer c.mu.RUnlock()
	header := map[string][]string{}
	for i, v := range c.header {
		header[i] = v
	}
	return header
}

// AddHead 添加请求头header
func (c *Client) AddHead(key string, val ...string) *Client {
	c.mu.Lock()
	defer c.mu.Unlock()
	c.header[key] = append(c.header[key], val...)
	return c
}

// AddHeads 批量添加请求头header
func (c *Client) AddHeads(m map[string][]string) *Client {
	for i, v := range m {
		c.AddHead(i, v...)
	}
	return c
}

// SetHead 设置请求头header,已存在则覆盖
func (c *Client) SetHead(key string, val ...string) *Client {
	c.mu.Lock()
	defer c.mu.Unlock()
	c.header[key] = val
	return c
}

// SetHeads 批量设置请求头header,,已存在则覆盖
func (c *Client) SetHeads(m map[string][]string) *Client {
	header := http.Header{}
	for i, v := range m {
		header[i] = v
	}
	c.header = header
	return c
}

// AddCookie 添加请求头cookie
func (c *Client) AddCookie(s ...*http.Cookie) *Client {
	c.cookie = append(c.cookie, s...)
	return c
}

// SetCookie 设置请求头cookie
func (c *Client) SetCookie(s ...*http.Cookie) *Client {
	c.cookie = s
	return c
}

// SetUserAgent 设置User-Agent
func (c *Client) SetUserAgent(s string) *Client {
	return c.SetHead("User-Agent", s)
}

// SetReferer 设置Referer
func (c *Client) SetReferer(s string) *Client {
	return c.SetHead("Referer", s)
}

// SetToken 设置请求头Authorization,别名
func (c *Client) SetToken(s string) *Client {
	return c.SetAuthorization(s)
}

// SetAuthorization 设置请求头Authorization
func (c *Client) SetAuthorization(s string) *Client {
	return c.SetHead(HeaderAuthorization, s)
}

// SetContentType 设置请求头Content-Type
func (c *Client) SetContentType(s string) *Client {
	return c.SetHead(HeaderContentType, s)
}

// FormFile form-data file
func (c *Client) FormFile(m map[string][]byte) *Client {
	body := new(bytes.Buffer)
	w := multipart.NewWriter(body)
	for i, v := range m {
		fileWriter, err := w.CreateFormFile(i, i)
		if err != nil {
			continue
		}
		fileWriter.Write(v)
	}
	_ = w.Close()
	c.SetContentType(w.FormDataContentType()).Body(body.Bytes())
	return c
}

// FormFieldFile form-data Field file
func (c *Client) FormFieldFile(field map[string]interface{}, file map[string][]byte) *Client {
	body := new(bytes.Buffer)
	w := multipart.NewWriter(body)
	for i, v := range field {
		_ = w.WriteField(i, toString(v))
	}
	for i, v := range file {
		fileWriter, err := w.CreateFormFile(i, i)
		if err != nil {
			continue
		}
		fileWriter.Write(v)
	}
	_ = w.Close()
	c.SetContentType(w.FormDataContentType()).Body(body.Bytes())
	return c
}

// FormField form-data Field
func (c *Client) FormField(m map[string]interface{}) *Client {
	body := new(bytes.Buffer)
	w := multipart.NewWriter(body)
	for i, v := range m {
		_ = w.WriteField(i, toString(v))
	}
	_ = w.Close()
	c.SetContentType(w.FormDataContentType()).Body(body.Bytes())
	return c
}

// Body 设置请求body,默认json解析
func (c *Client) Body(i interface{}) *Client {
	c.body = toBytes(i)
	return c
}

// Bind 解析响应body
func (c *Client) Bind(i interface{}) *Client {
	c.bodyBind = i
	return c
}

// SetTimeout 设置请求超时时间
func (c *Client) SetTimeout(t time.Duration) *Client {
	if c.Client != nil {
		c.Client.Timeout = t
	}
	return c
}

// Debug 调试模式,打印请求响应详情
func (c *Client) Debug(debug ...bool) *Client {
	c.debug = !(len(debug) > 0 && !debug[0])
	return c
}

// Retry 重试次数默认不重试
func (c *Client) Retry(num ...uint) *Client {
	if len(num) > 0 {
		c.retry = num[0]
	}
	return c
}

func (c *Client) GetBytes() ([]byte, error) {
	resp := c.Get()
	return resp.GetBody(), resp.Err()
}

func (c *Client) Get() *Response {
	return c.Do(MethodGet)
}

func (c *Client) Post() *Response {
	return c.Do(MethodPost)
}

func (c *Client) Put() *Response {
	return c.Do(MethodPut)
}

func (c *Client) Delete() *Response {
	return c.Do(MethodDelete)
}

func (c *Client) Request(method string) (*Request, error) {
	request, err := NewRequest(method, dealQuery(c.url, c.query), c.body)
	if err != nil {
		return nil, err
	}
	defer request.Body.Close()
	request.Request.Header = c.copyHead()
	for _, v := range c.cookie {
		request.AddCookie(v)
	}
	c.try++
	request.AddHead(HeaderTry, toString(c.try))
	return request, nil
}

func (c *Client) Do(method string) (resp *Response) {
	start := time.Now()
	defer func() {
		if c.done() {
			c.reset()
			c.SetCookie(resp.Cookies()...)
		}
	}()
	request, err := c.Request(method)
	if err != nil {
		return newResponse(nil, nil, err)
	}
	r, err := c.Client.Do(request.Request)
	resp = newResponse(request, r, err).Bind(c.bodyBind).
		setSpend(start).setTryNum(int(c.try)).print(c.debug)
	if resp.Err() != nil && !c.done() {
		return c.Do(method)
	}
	return
}
